﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Common.Entitys.CommonLogs;
using Common.Entitys.Messages;
using Common.Entitys.S7;
using Common.Utlis;
using S7.Net;
using S7.Net.Types;
using static Common.Entitys.S7.S7Enums;
using Byte = System.Byte;
using DateTime = System.DateTime;

namespace Common.Utils.S7Help
{
    public  class S7CommunicationHelp
    {

        private static  S7CommunicationHelp m_Instance = new S7CommunicationHelp();

        public static S7CommunicationHelp Instance => m_Instance;

        private S7CommunicationHelp()
        {
            
        }

        /// <summary>
        /// 定义PLC
        /// </summary>
        public  Plc _plc = null;

        public Plc _plcWrite = null;

        public bool IsConnected = false;
        /// <summary>
        /// 连接PLC
        /// </summary>
        /// <param name="type">PLC CPU类型</param>
        /// <param name="IP">PLC服务器IP</param>
        /// <param name="Rack">机台号</param>
        /// <param name="Slot">插槽号</param>
        /// <returns></returns>
        public bool PLCConnect(PLC_CPUType type, string IP, short Rack, short Slot)
        {
            CpuType cpuType = (CpuType)Enum.Parse(typeof(CpuType), type.ToString());
            //实例化
            _plc = new Plc(cpuType, IP, Rack, Slot);
            _plcWrite = new Plc(cpuType, IP, Rack, Slot);
            IsConnected = false;
            try
            {
                _plc.Open();
                _plcWrite.Open();
                IsConnected = _plc.IsConnected;
                if (IsConnected)
                {
                    LogHelp.AddLog<InfoLogEntity>($"TCP服务器 IP:{IP} 机台号{Rack} 插槽号{Slot} 连接成功！ ");
                }
                else
                {
                    LogHelp.AddLog<InfoLogEntity>($"TCP服务器 IP:{IP} 机台号{Rack} 插槽号{Slot} 连接失败！ ");
                }

            }
            catch (Exception ex)
            {
                LogHelp.AddLog<ErrorLogEntity>($"PLC连接失败：{ex.Message} ");
            }
            return IsConnected;
        }

        /// <summary>
        /// 断开
        /// </summary>
        public void PLCDisConnect()
        {
            if (_plc != null)
            {
                _plc.Close();
                LogHelp.AddLog<InfoLogEntity>($"TCP服务器断开连接！ ");
            }
        }

        /// <summary>
        /// 根据Read方式读取
        /// </summary>
        /// <param name="variable"></param>
        /// <returns></returns>
        public object PLC_Read(string variable)
        {
            object result = null;
            try
            {
                result= _plc.Read(variable);
            }
            catch (Exception ex)
            {
                LogHelp.AddLog<ErrorLogEntity>($"PLC通过read方式读取失败：{ex.Message} ");
            }

            return result;
    
        }

        /// <summary>
        /// 写DataItem
        /// </summary>
        /// <param name="dataItem"></param>
        public void PLC_Read(DataItem dataItem)
        {
            List<DataItem> lis = new List<DataItem>();
            lis.Add(dataItem);
            _plc.ReadMultipleVars(lis);
        }

        public void StartRead(Dictionary<string, DataItem> dictDataItems)
        {
            //备份的上次记录
            Dictionary<string, DataItem> dictDataItemsBackup = DeepCopy.DeepCopyByJson(dictDataItems);
            List<DataItem> lisDataItems= dictDataItems.Values.ToList();


            Task.Factory.StartNew(() =>
            {
                bool t = false;
                while (true)
                {
                    if (t)
                    {
                        Thread.Sleep(50);
                        continue;
                    }
                    DateTime start1= DateTime.Now;
                    _plc.ReadMultipleVars(lisDataItems);
                    DateTime start2 = DateTime.Now;
                    Dictionary<string, DataItem> dictDataItemsTemp = new Dictionary<string, DataItem>(lisDataItems.Count);
                    int i = 0;
                    foreach (KeyValuePair<string, DataItem> keyValuePair in dictDataItemsBackup)
                    {
                        try
                        {
                            DataItem currentDataItem = lisDataItems[i];
                            dictDataItemsTemp.Add(keyValuePair.Key, currentDataItem);
                            //不变更的属性不发布
                            if (Equals(keyValuePair.Value.Value, currentDataItem.Value))
                            {
                                continue;
                            }

                            //if (keyValuePair.Value.Value == currentDataItem.Value)
                            //{
                            //    continue;
                            //}

                            //if (currentDataItem.VarType == VarType.Bit)
                            //{
                            //    int ran = new Random(DateTime.Now.Millisecond).Next(0,1000);
                            //    if (ran % 2 == 0)
                            //    {
                            //        currentDataItem.Value =true;
                            //    }
                            //    else
                            //    {
                            //        currentDataItem.Value = false;
                            //    }
                                
                            //}

                            //发布出去消息
                            MessageAggregator<DataItem>.Publish(currentDataItem, keyValuePair.Key);
                        }
                        catch (Exception e)
                        {
                           //to do 
                        }
                        finally
                        {
                            i++;
                        }
                    }
                    //保留备份数据
                    dictDataItemsBackup.Clear();
                    dictDataItemsBackup = DeepCopy.DeepCopyByJson(dictDataItemsTemp);
                  
                    TimeSpan ts1 = start2 - start1;
                    TimeSpan ts2=DateTime.Now- start2;
                    Thread.Sleep(20);

                }
            });
        }

        /// <summary>
        /// 写DataItem
        /// </summary>
        /// <param name="dataItem"></param>
        public void PLC_Read(List<DataItem> lisDataItems)
        {
            _plc.ReadMultipleVars(lisDataItems);
        }


        /// <summary>
        /// 根据ReadBytes方式读取
        /// </summary>
        /// <param name="dataType">s7数据类型</param>
        /// <param name="db">第几个DB块</param>
        /// <param name="startByteAdr">开始的偏移量位置</param>
        /// <param name="count">字节数</param>
        /// <param name="variables">PLC变量列表</param>
        /// <returns></returns>
        public List<Variable> PLC_ReadBytes(DataType dataType, int db, int startByteAdr, int count, List<Variable> variables)
        {
            try
            {
                Byte[] data = _plc.ReadBytes(dataType, db, startByteAdr, count);
                foreach (var item in variables)
                {
                    switch (item.DataTypes)
                    {
                        case DataTypes.Bool:
                            //先获取StartIndex 再获取offset
                            int startIndex = Convert.ToInt32(item.Start.Split(".")[0]) - startByteAdr;
                            int offset = Convert.ToInt32(item.Start.Split(".")[1]);
                            item.VarValue = DataConvertLib.GetBitFromByte(data[startIndex], offset);
                            break;
                        case DataTypes.Byte:
                            item.VarValue = DataConvertLib.GetByteFromByteArray(data, Convert.ToInt32(item.Start));
                            break;
                        case DataTypes.Short:
                            item.VarValue = DataConvertLib.GetShortFromByteArray(data, Convert.ToInt32(item.Start));
                            break;
                        case DataTypes.UShort:
                            item.VarValue = DataConvertLib.GetUShortFromByteArray(data, Convert.ToInt32(item.Start));
                            break;
                        case DataTypes.Int:
                            item.VarValue = DataConvertLib.GetIntFromByteArray(data, Convert.ToInt32(item.Start));
                            break;
                        case DataTypes.UInt:
                            item.VarValue = DataConvertLib.GetUIntFromByteArray(data, Convert.ToInt32(item.Start));
                            break;
                        case DataTypes.Float:
                            item.VarValue = DataConvertLib.GetFloatFromByteArray(data, Convert.ToInt32(item.Start));
                            break;
                        case DataTypes.Double:
                            item.VarValue = DataConvertLib.GetDoubleFromByteArray(data, Convert.ToInt32(item.Start));
                            break;
                        case DataTypes.Long:
                            item.VarValue = DataConvertLib.GetLongFromByteArray(data, Convert.ToInt32(item.Start));
                            break;
                        case DataTypes.ULong:
                            item.VarValue = DataConvertLib.GetULongFromByteArray(data, Convert.ToInt32(item.Start));
                            break;
                        case DataTypes.String:
                            item.VarValue = DataConvertLib.GetStringFromByteArrayByBitConvert(data, Convert.ToInt32(item.Start), 0);
                            break;

                    }
                }
            }
            catch (Exception ex)
            {
                LogHelp.AddLog<ErrorLogEntity>($"PLC通过read方式读取失败：{ex.Message} ");
            }

            return variables;

        }



        /// <summary>
        /// 根据ReadClass方式读取
        /// </summary>
        /// <param name="sourceClass">实体类</param>
        /// <param name="db">第几个DB块</param>
        /// <param name="startByteAdr">开始的偏移量位置</param>
        /// <returns></returns>

        public int PLC_ReadClass(object sourceClass, int db, int startByteAdr = 0)
        {
            int result = 0;
            try
            {
                result = _plc.ReadClass(sourceClass, db, startByteAdr);
            }
            catch (Exception ex)
            {
                LogHelp.AddLog<ErrorLogEntity>($"PLC通过ReadClass方式读取失败：{ex.Message} ");
            }

            return result;
        }

        /// <summary>
        /// 根据ReadClass方式读取
        /// </summary>
        /// <param name="sourceClass">实体类</param>
        /// <param name="db">第几个DB块</param>
        /// <param name="startByteAdr">开始的偏移量位置</param>
        /// <returns></returns>

        public int PLC_ReadClass<T>(object sourceClass) where T : BaseDB
        {
            int result = 0;
            Type type = typeof(T);
  
            int db = S7AttributeHelp.GetDB<T>();
            PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
            S7PropertyAttribute s7Property = (S7PropertyAttribute)Attribute.GetCustomAttribute(propertyInfos[0], typeof(S7PropertyAttribute));
            //不是S7的属性  直接跳过
            if (s7Property != null)
            {
                try
                {
                    result = _plc.ReadClass(sourceClass, db, s7Property.StartByteAdr);
                }
                catch (Exception ex)
                {
                    LogHelp.AddLog<ErrorLogEntity>($"PLC通过ReadClass方式读取失败：{ex.Message} ");
                }
            }
            return result;
        }



        /// <summary>
        /// 根据ReadClass方式读取
        /// </summary>
        /// <param name="sourceStruct">结构体</param>
        /// <param name="db">第几个DB块</param>
        /// <param name="startByteAdr">开始的偏移量位置</param>
        /// <returns></returns>

        public object PLC_ReadStruct(Type structType, int db, int startByteAdr = 0)
        {
            Object obj=null;
            try
            {
        
                 obj = _plc.ReadStruct(structType, db, startByteAdr);

            }
            catch (Exception ex)
            {
                LogHelp.AddLog<ErrorLogEntity>($"PLC通过ReadClass方式读取失败：{ex.Message} ");
            }

            return obj;
        }

        /// <summary>
        /// 根据Write方式写入
        /// </summary>
        /// <param name="dataTypes">数据类型</param>
        /// <param name="variable">DB块地址</param>
        /// <param name="value">写入值</param>
        public void PLC_Write(DataTypes dataTypes, string variable, object value)
        {
            try
            {
                switch (dataTypes)
                {
                    case DataTypes.Bool:
                        _plc.Write(variable, Convert.ToBoolean(value));
                        break;
                    case DataTypes.Byte:
                        _plc.Write(variable, Convert.ToByte(value));
                        break;
                    case DataTypes.Short:
                        _plc.Write(variable, Convert.ToSByte(value));
                        break;
                    case DataTypes.UShort:
                        _plc.Write(variable, Convert.ToBoolean(value));
                        break;
                    case DataTypes.Int:
                        _plc.Write(variable, Convert.ToInt16(value));
                        break;
                    case DataTypes.UInt:
                        _plc.Write(variable, Convert.ToUInt16(value));
                        break;
                    case DataTypes.Float:
                        _plc.Write(variable, Convert.ToSingle(value));
                        break;
                    case DataTypes.Double:
                        _plc.Write(variable, Convert.ToDouble(value));
                        break;

                }

            }
            catch (Exception ex)
            {
                LogHelp.AddLog<ErrorLogEntity>($"PLC通过Write方式写入失败：{ex.Message} ");
            }

        }

        /// <summary>
        /// 写DataItem
        /// </summary>
        /// <param name="dataItem"></param>
        public void PLC_Write(DataItem dataItem)
        {
            //_plc.Write(dataItem);
            _plcWrite.Write(dataItem);
        }

       /// <summary>
       /// 写单个属性
       /// </summary>
       /// <typeparam name="T">DB类类型</typeparam>
       /// <typeparam name="F">DB类属性类型</typeparam>
       /// <param name="expression">表达式树</param>
       /// <param name="f">属性的值</param>
        public bool PLC_Write<T, F>(Expression<Func<T, F>> expression, F f) where T : BaseDB
        {

            DataItem dataItem = S7DataItemHelp.GetDataItem(expression, f);
            try
            {
                DateTime startTime = DateTime.Now;
                PLC_Write(dataItem);
                Debug.WriteLine($"{(DateTime.Now - startTime).TotalMilliseconds}   ");
                return true;
            }
            catch (Exception e)
            {
                MessageAggregator<PromptMsg>.Publish(e.Message);
                return false;
            }
          
        }

        /// <summary>
        /// 写一堆
        /// </summary>
        /// <param name="dataItems"></param>
        public bool PLC_Write(DataItem[] dataItems)
        {
            try
            {
                DateTime startTime=DateTime.Now;
                _plcWrite.Write(dataItems);
                Debug.WriteLine($"{(DateTime.Now-startTime).TotalMilliseconds}   ");
                return true;
            }
            catch (Exception e)
            {
                MessageAggregator<PromptMsg>.Publish(e.Message);
                return false;
            }
            
        }

        /// <summary>
        /// 根据WriteBytes方式写入
        /// </summary>
        /// <param name="dataType">s7数据类型</param>
        /// <param name="db">第几个DB块</param>
        /// <param name="startByteAdr">开始的偏移量位置</param>
        /// <param name="value">数据值</param>
        public void PLC_WriteBytes(DataType dataType, int db, int startByteAdr, byte[] value)
        {
            try
            {
                _plc.WriteBytes(dataType, db, startByteAdr, value);
            }
            catch (Exception ex)
            {
                LogHelp.AddLog<ErrorLogEntity>($"PLC通过WriteBytes方式写入失败：{ex.Message} ");
            }

        }

        /// <summary>
        /// 根据WriteClass方式写入
        /// </summary>
        /// <param name="classValue">实体类</param>
        /// <param name="db">第几个DB块</param>
        /// <param name="startByteAdr">开始的偏移量位置</param>
        public void PLC_WriteClass(object classValue, int db, int startByteAdr = 0)
        {
            try
            {
                _plc.WriteClass(classValue, db, startByteAdr);

            }
            catch (Exception ex)
            {
                LogHelp.AddLog<ErrorLogEntity>($"PLC通过WriteClass方式写入失败：{ex.Message} ");
            }
        }

        /// <summary>
        /// 根据WriteStruct方式写入
        /// </summary>
        /// <param name="structValue">结构体</param>
        /// <param name="db">第几个DB块</param>
        /// <param name="startByteAdr">开始的偏移量位置</param>
        public void PLC_WriteStruct(object structValue, int db, int startByteAdr = 0)
        {
            try
            {
                _plc.WriteStruct(structValue, db,startByteAdr);

            }
            catch (Exception ex)
            {
                LogHelp.AddLog<ErrorLogEntity>($"PLC通过WriteStruct方式写入失败：{ex.Message} ");
            }
        }
    }
}
